package com.stripe.android.lpmfoundations.paymentmethod.link

import android.util.Log
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.stripe.android.DefaultCardBrandFilter
import com.stripe.android.core.Logger
import com.stripe.android.link.LinkConfiguration
import com.stripe.android.link.LinkConfigurationCoordinator
import com.stripe.android.link.LinkPaymentDetails
import com.stripe.android.link.account.FakeLinkAccountManager
import com.stripe.android.link.account.LinkAccountManager
import com.stripe.android.link.analytics.FakeLinkEventsReporter
import com.stripe.android.link.attestation.LinkAttestationCheck
import com.stripe.android.link.gate.LinkGate
import com.stripe.android.link.injection.LinkComponent
import com.stripe.android.link.injection.LinkInlineSignupAssistedViewModelFactory
import com.stripe.android.link.model.AccountStatus
import com.stripe.android.link.theme.DefaultLinkTheme
import com.stripe.android.link.ui.inline.InlineSignupViewModel
import com.stripe.android.link.ui.inline.LINK_INLINE_SIGNUP_REMAINING_FIELDS_TEST_TAG
import com.stripe.android.link.ui.inline.LinkSignupMode
import com.stripe.android.link.ui.inline.SignUpConsentAction
import com.stripe.android.link.ui.inline.UserInput
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadataFixtures
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodSaveConsentBehavior
import com.stripe.android.lpmfoundations.paymentmethod.PaymentSheetCardFundingFilter
import com.stripe.android.model.ConsumerSession
import com.stripe.android.model.LinkMode
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.payments.financialconnections.FinancialConnectionsAvailability
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.testing.PaymentIntentFactory
import com.stripe.android.testing.createComposeCleanupRule
import com.stripe.android.utils.FakeLinkComponent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import org.junit.Rule
import org.junit.runner.RunWith
import kotlin.test.Test

@RunWith(AndroidJUnit4::class)
class LinkFormElementTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @get:Rule
    val composeCleanupRule = createComposeCleanupRule()

    @Test
    fun `If initial user input is provided, should be displayed to the user when alongside SFU`() {
        val element = createLinkFormElement(
            signupMode = LinkSignupMode.AlongsideSaveForFutureUse,
            initialLinkUserInput = UserInput.SignUp(
                name = "John Doe",
                email = "email@email.com",
                phone = "+11234567890",
                country = "CA",
                consentAction = SignUpConsentAction.Checkbox,
            ),
        )

        composeTestRule.setContent {
            DefaultLinkTheme {
                element.ComposeUI(
                    enabled = true,
                    hiddenIdentifiers = emptySet(),
                    lastTextFieldIdentifier = null,
                )
            }
        }

        composeTestRule.waitForRemainingLinkFields()

        composeTestRule.hasLinkFieldWith(text = "John Doe")
        composeTestRule.hasLinkFieldWith(text = "email@email.com")
        composeTestRule.hasLinkFieldWith(text = "(123) 456-7890")
    }

    @Test
    fun `If initial user input is provided, should be expanded and displayed to the user when instead of SFU`() {
        val element = createLinkFormElement(
            signupMode = LinkSignupMode.InsteadOfSaveForFutureUse,
            initialLinkUserInput = UserInput.SignUp(
                name = "John Doe",
                email = "email@email.com",
                phone = "+11234567890",
                country = "CA",
                consentAction = SignUpConsentAction.Checkbox,
            ),
        )

        composeTestRule.setContent {
            DefaultLinkTheme {
                element.ComposeUI(
                    enabled = true,
                    hiddenIdentifiers = emptySet(),
                    lastTextFieldIdentifier = null,
                )
            }
        }

        composeTestRule.waitForRemainingLinkFields()

        composeTestRule.hasLinkFieldWith(text = "John Doe")
        composeTestRule.hasLinkFieldWith(text = "email@email.com")
        composeTestRule.hasLinkFieldWith(text = "(123) 456-7890")
    }

    private fun ComposeTestRule.hasLinkFieldWith(text: String) {
        onNodeWithText(text).assertExists()
    }

    private fun ComposeTestRule.waitForRemainingLinkFields() {
        waitUntil(timeoutMillis = 5000L) {
            onAllNodesWithTag(testTag = LINK_INLINE_SIGNUP_REMAINING_FIELDS_TEST_TAG)
                .fetchSemanticsNodes()
                .isNotEmpty()
        }
    }

    private fun createLinkFormElement(
        signupMode: LinkSignupMode,
        initialLinkUserInput: UserInput?,
    ): LinkFormElement {
        return LinkFormElement(
            signupMode = signupMode,
            configuration = createConfiguration(),
            initialLinkUserInput = initialLinkUserInput,
            linkConfigurationCoordinator = createLinkConfigurationCoordinator(),
            onLinkInlineSignupStateChanged = {},
            previousLinkSignupCheckboxSelection = null,
        )
    }

    private fun createConfiguration(): LinkConfiguration {
        return LinkConfiguration(
            stripeIntent = PaymentIntentFactory.create(),
            merchantName = "Merchant, Inc.",
            sellerBusinessName = null,
            merchantCountryCode = "CA",
            merchantLogoUrl = null,
            customerInfo = LinkConfiguration.CustomerInfo(
                name = "John Doe",
                email = null,
                phone = null,
                billingCountryCode = "CA",
            ),
            shippingDetails = null,
            passthroughModeEnabled = false,
            cardBrandChoice = null,
            cardBrandFilter = DefaultCardBrandFilter,
            financialConnectionsAvailability = FinancialConnectionsAvailability.Full,
            flags = mapOf(),
            useAttestationEndpointsForLink = false,
            suppress2faModal = false,
            elementsSessionId = "session_1234",
            linkMode = LinkMode.LinkPaymentMethod,
            allowDefaultOptIn = false,
            disableRuxInFlowController = false,
            defaultBillingDetails = null,
            billingDetailsCollectionConfiguration = PaymentSheet.BillingDetailsCollectionConfiguration(),
            collectMissingBillingDetailsForExistingPaymentMethods = true,
            allowUserEmailEdits = true,
            allowLogOut = true,
            enableDisplayableDefaultValuesInEce = false,
            skipWalletInFlowController = false,
            linkAppearance = null,
            linkSignUpOptInFeatureEnabled = false,
            linkSignUpOptInInitialValue = false,
            customerId = null,
            saveConsentBehavior = PaymentMethodSaveConsentBehavior.Legacy,
            forceSetupFutureUseBehaviorAndNewMandate = false,
            linkSupportedPaymentMethodsOnboardingEnabled = listOf("CARD"),
            clientAttributionMetadata = PaymentMethodMetadataFixtures.CLIENT_ATTRIBUTION_METADATA,
            cardFundingFilter = PaymentSheetCardFundingFilter(PaymentSheet.CardFundingType.entries),
        )
    }

    private fun createLinkConfigurationCoordinator(): LinkConfigurationCoordinator {
        return FakeLinkConfigurationCoordinator
    }

    private object FakeLinkConfigurationCoordinator : LinkConfigurationCoordinator {
        override val emailFlow: StateFlow<String?>
            get() {
                error("Not implemented!")
            }

        override fun getComponent(configuration: LinkConfiguration): LinkComponent {
            val linkAccountManager = FakeLinkAccountManager()
            return FakeLinkComponent(
                configuration = configuration,
                linkAccountManager = linkAccountManager,
                inlineSignupViewModelFactory = FakeLinkInlineSignupAssistedViewModelFactory(
                    linkAccountManager = linkAccountManager,
                    configuration = configuration,
                )
            )
        }

        override fun getAccountStatusFlow(configuration: LinkConfiguration): Flow<AccountStatus> {
            error("Not implemented!")
        }

        override fun linkGate(configuration: LinkConfiguration): LinkGate {
            error("Not implemented!")
        }

        override fun linkAttestationCheck(configuration: LinkConfiguration): LinkAttestationCheck {
            error("Not implemented!")
        }

        override suspend fun signInWithUserInput(
            configuration: LinkConfiguration,
            userInput: UserInput
        ): Result<Boolean> {
            error("Not implemented!")
        }

        override suspend fun attachNewCardToAccount(
            configuration: LinkConfiguration,
            paymentMethodCreateParams: PaymentMethodCreateParams
        ): Result<LinkPaymentDetails> {
            error("Not implemented!")
        }

        override suspend fun logOut(configuration: LinkConfiguration): Result<ConsumerSession> {
            error("Not implemented!")
        }
    }

    private class FakeLinkInlineSignupAssistedViewModelFactory(
        private val linkAccountManager: LinkAccountManager,
        private val configuration: LinkConfiguration,
    ) : LinkInlineSignupAssistedViewModelFactory {
        override fun create(
            signupMode: LinkSignupMode,
            initialUserInput: UserInput?,
            previousLinkSignupCheckboxSelection: Boolean?
        ): InlineSignupViewModel {
            return InlineSignupViewModel(
                signupMode = signupMode,
                config = configuration,
                initialUserInput = initialUserInput,
                linkAccountManager = linkAccountManager,
                linkEventsReporter = FakeLinkInlineSignupEventsReporter,
                logger = Logger.noop(),
                lookupDelay = 0L,
                previousLinkSignupCheckboxSelection = previousLinkSignupCheckboxSelection,
            )
        }
    }

    private object FakeLinkInlineSignupEventsReporter : FakeLinkEventsReporter() {
        override fun onSignupStarted(isInline: Boolean) {
            Log.d("LINK_FORM_ELEMENT_TEST", "onSignupStarted")
        }

        override fun onInlineSignupCheckboxChecked() {
            Log.d("LINK_FORM_ELEMENT_TEST", "onInlineSignupCheckboxChecked")
        }
    }
}
